/* ppp_ctrl.c
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/

/** returns 1 if line starts with prefix, 0 if it does not */


#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "cutils/properties.h"
#include <telephony/ril.h>
#include "ril_util.h"
#include "ppp_ctrl.h"

#define LOG_TAG "RIL"
#include <utils/Log.h>
#include "uevent.h"

#define PATH_PPP_OPERSTATE 		"/sys/class/net/ppp0/operstate"
#define UNKNOWN_ASCII_STRING 	"unknown"
#define UP_ASCII_STRING			"up"
#define DOWN_ASCII_STRING		"down"

static const struct timeval TIMEVAL_GPRSTPOLL = {5, 0};

struct _PppCtrl
{
	PppState				state;
	int                     started_poll;
	int 					try_destroy;

	OnPppStateChangedFunc	onStateChanged;
	void*					user_data;
	
	OnUeventFunc			onUevent;	
	void*					uevent_udata;
	
	pthread_t 				tid_pppd_monitor;
	int						with_thread;
	
	pthread_t				tid_dev_monitor;
	int 					dev_index;
	char 					dev_name[128];
	int 					index_setted;
	int 					usb_available;
};


extern void RIL_requestTimedCallback (RIL_TimedCallback callback,
				void *param, const struct timeval *relativeTime);
static void ppp_ctrl_try_destroy(PppCtrl* thiz);
static void start_interface_waiting_pthread(PppCtrl* thiz);
static void ppp_uevent_handler(void* data, const char* msg, int msg_len);
static void ppp_ctrl_dump(PppCtrl* thiz);

static PppState get_operater_state(PppCtrl *thiz)
{
	PppState ret = PPP_STATE_IDLE;
	char buffer[65];
	char path[65] = {0};
	snprintf(path, 64, "/sys/class/net/ppp%d/operstate", thiz->dev_index);
#ifdef ENABLE_LOG
	//ALOGD("QUERY STATE FILE :%s\n", path);
#endif
	FILE* fp = fopen(path, "rb");
	//FILE* fp = fopen(PATH_PPP_OPERSTATE, "rb");

	if(fp != NULL)
	{
		long len = 0;
		
		memset(buffer, 0, 65);
		fseek(fp, 0, SEEK_END);
		len = ftell(fp);
		len = (len > 64) ? 64 : len;
		fseek(fp, 0, SEEK_SET);
		fread(buffer, len, 1, fp);
		fclose(fp);

		//ALOGD("%s: ppp state=%s \n", __func__, buffer);
		
		if(strStartsWith(buffer, UNKNOWN_ASCII_STRING)
			|| strStartsWith(buffer, UP_ASCII_STRING)) {
			ret = PPP_STATE_UP;
		} else if(strStartsWith(buffer, DOWN_ASCII_STRING)) {
			ret = PPP_STATE_DOWN;
		}
	}

#ifdef ENABLE_LOG
	ALOGD("%s %p PPPD ENTER STATE:%d\n", __func__, thiz, ret);
#endif
	return ret;
}

static void ppp_poll_state(void* data)
{
	PppCtrl* thiz = (PppCtrl*)data;
	PppState state = get_operater_state(thiz);

	if(thiz->state != state)
	{
		thiz->state = state;
		thiz->onStateChanged(thiz, state, thiz->user_data);
	}

	if(thiz->state != PPP_STATE_IDLE) 
	{
		RIL_requestTimedCallback(ppp_poll_state, thiz, &TIMEVAL_GPRSTPOLL);		
	}
	else
	{
		thiz->onStateChanged(thiz, state, thiz->user_data);
		thiz->started_poll = 0;
		ppp_ctrl_try_destroy(thiz);
	}
#ifdef ENABLE_LOG
	ppp_ctrl_dump(thiz);
#endif

	return;
}

static void *pppdStateMonitor(void *arg)
{
	PppCtrl* thiz = (PppCtrl*)arg;
	PppState state = PPP_STATE_IDLE;

	while(1)
	{
		sleep(3);
		state = get_operater_state(thiz);
		
		if(thiz->state != state)
		{
			thiz->state = state;
			thiz->onStateChanged(thiz, state, thiz->user_data);
		}

		if(thiz->state != PPP_STATE_IDLE) 
		{
			sleep(5);
			//RIL_requestTimedCallback(ppp_poll_state, thiz, &TIMEVAL_GPRSTPOLL);		
		}
		else
		{
			thiz->onStateChanged(thiz, state, thiz->user_data);
			thiz->started_poll = 0;
			break;
		}
	}
#ifdef ENABLE_LOG
	ALOGD("Oops! Out of state query loop!!!!!!!!!!");
#endif
	ppp_ctrl_try_destroy(thiz);

	return NULL;
}

char* ppp_ctrl_get_dev_name(PppCtrl* thiz)
{
#ifdef ENABLE_LOG
	ALOGD("%s: %p\n", __func__, thiz);
#endif
	if(thiz != NULL && thiz->index_setted)
	{
#ifdef ENABLE_LOG
		ALOGD("%s: %p index setted:%d %s\n", __func__, thiz, thiz->index_setted, thiz->dev_name);
#endif
		return thiz->dev_name;	
	}

	return NULL;
}

void ppp_ctrl_set_uevent_listener(PppCtrl* thiz, OnUeventFunc onUevent, void* user_data)
{
#ifdef ENABLE_LOG
	ALOGD("%s: %p\n", __func__, thiz);
#endif
	if(thiz != NULL) {
		thiz->onUevent = onUevent;
		thiz->uevent_udata = user_data;
	}
}

PppCtrl* 	ppp_ctrl_create(OnPppStateChangedFunc onStateChanged, void* user_data)
{
#ifdef ENABLE_LOG
	ALOGD("%s\n", __func__);
#endif
	PppCtrl* thiz = NULL;

	if(onStateChanged != NULL)
	{
		thiz = calloc(1, sizeof(PppCtrl));
		thiz->state = PPP_STATE_IDLE;
		thiz->onStateChanged = onStateChanged;
		thiz->user_data = user_data;
		thiz->started_poll = 0;
		thiz->index_setted = 0;
#ifdef ENABLE_LOG
		ALOGD("%s %d ppp:%p, callback:%p", __func__, __LINE__,  thiz, onStateChanged);
#endif
		uevent_add_native_handler(ppp_uevent_handler, (void *)thiz);
		//start_interface_waiting_pthread(thiz); 
	}
#ifdef ENABLE_LOG
		ALOGD("%s %d ppp:%p", __func__, __LINE__,  thiz);
#endif
	return thiz;
}

static void ppp_ctrl_try_destroy(PppCtrl* thiz)
{
	if(thiz->started_poll == 0 && thiz->try_destroy == 1)
	{
#ifdef ENABLE_LOG
		ALOGD("Oops! PppCtrl destroyed!!!!!!!!!!");
#endif
		free(thiz);
	}

	return;
}

void		ppp_ctrl_destroy(PppCtrl* thiz)
{
	if(thiz != NULL)
	{
		thiz->try_destroy = 1;
		ppp_ctrl_try_destroy(thiz);
	}

	return;
}

PppState 	ppp_ctrl_get_state(PppCtrl* thiz)
{
	if(thiz != NULL)
	{
		return thiz->state;
	}
	else
	{
		return PPP_STATE_IDLE;
	}
}

static void ppp_ctrl_dump(PppCtrl* thiz)
{
#ifdef ENABLE_LOG
	if(thiz != NULL)
	{
		//ALOGD("{PppCtl: %p dev_index:%d, dev_name:%s, index_setted:%d with_thread:%d\n", thiz, thiz->dev_index, thiz->dev_name, thiz->index_setted, thiz->with_thread);
	}
#endif
}

#define PPP_DEVICE_ADD_EVENT "add@/devices/virtual/net/ppp"
#define PPP_DEVICE_RM_EVENT "remove@/devices/virtual/net/ppp"
static void ppp_interface_poll_handle_uevent(PppCtrl* thiz, const char* msg, int msg_len) 
{
	if(strlen(msg) == strlen(PPP_DEVICE_ADD_EVENT) + 1
		&& strStartsWith(msg, PPP_DEVICE_ADD_EVENT))
	{
#ifdef ENABLE_LOG
		ALOGD("%s: msg=%s", __func__, msg);
#endif
		sscanf(msg, PPP_DEVICE_ADD_EVENT"%d", &thiz->dev_index);
		thiz->index_setted = 1;
		//snprintf(thiz->dev_name, sizeof(thiz->dev_name), "ppp%d", thiz->dev_index);
		snprintf(thiz->dev_name, 128, "ppp%d", thiz->dev_index);
		if(!thiz->with_thread && thiz->started_poll) 
		{
			//ALOGD("NOW WE POLL STATE FOR RILD, YOU HAVE TO CHECK IT");
			ppp_poll_state(thiz);
		}
	} else if(strlen(msg) == strlen(PPP_DEVICE_RM_EVENT) + 1
		&& strStartsWith(msg, PPP_DEVICE_RM_EVENT)) {
#ifdef ENABLE_LOG
		ALOGD("%s: msg=%s", __func__, msg);
#endif
		int index = 0; 	
		sscanf(msg, PPP_DEVICE_ADD_EVENT"%d", &index);
		if(thiz->dev_index == index)
		{
			thiz->index_setted = 0;
		}
		memset(thiz->dev_name, 0, sizeof(thiz->dev_name));
		if(!thiz->with_thread && thiz->started_poll)
		{
			//ALOGD("NOW WE POLL STATE FOR RILD, YOU HAVE TO CHECK IT");
			ppp_poll_state(thiz);
		}
	}
#ifdef ENABLE_LOG
	ppp_ctrl_dump(thiz);
#endif

	return;
}

/*
static void ppp_interface_poll_handle_uevent(PppCtrl* thiz, const char* buffer) 
{
	if(strlen(buffer) == strlen(PPP_DEVICE_ADD_EVENT) + 1)
	{
		ALOGD("%s: buffer=%s", __func__, buffer);
		sscanf(buffer, PPP_DEVICE_ADD_EVENT"%d", &thiz->dev_index);
		thiz->index_setted = 1;
		snprintf(thiz->dev_name, sizeof(thiz->dev_name), "ppp%d", thiz->dev_index);
	} 

	return;
}
*/

static void ppp_uevent_handler(void* data, const char* msg, int msg_len)
{
	PppCtrl* thiz = (PppCtrl *)data;
	ppp_interface_poll_handle_uevent(thiz, msg, msg_len);
}

#define UEVENT_LINE_LEN	256
static void* ppp_interface_poll_thread(void* arg) 
{
	PppCtrl* thiz = (PppCtrl *)arg;
	char buffer[UEVENT_LINE_LEN];

	//if(uevent_get_fd() < 0) {
#ifdef ENABLE_LOG
	ALOGD("%s call uevent_init()\n", __func__);
#endif
	uevent_init();
	//}
	memset(buffer, 0, UEVENT_LINE_LEN);
	
	while(1)
	{
		if(uevent_next_event(buffer, UEVENT_LINE_LEN - 1))
		{
#ifdef ENABLE_LOG
			ALOGD("%s Handle Uevent %s %s\n", __func__, buffer, thiz->onUevent == NULL ? "Call onUevent " : "onUevent is NULL");
#endif
			if(thiz->onUevent != NULL) {
				thiz->onUevent(thiz, buffer, thiz->uevent_udata);
			}
			else if(thiz->with_thread)
			{
#ifdef ENABLE_LOG
				ALOGD("%s CALL BACK IS NULL!", __func__);
#endif
			}
			//TODO
			//ppp_interface_poll_handle_uevent(thiz, buffer);
		}
	}
	
	return NULL;
}

static void start_interface_waiting_pthread(PppCtrl* thiz)
{
	pthread_attr_t attr;
	pthread_attr_init (&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	pthread_create(&thiz->tid_dev_monitor, &attr, ppp_interface_poll_thread, thiz);
}

void        ppp_ctrl_start_pppd_with_call(PppCtrl* thiz, const char* device,
                const char* user, const char* pwd, const char* number, int authtype)
{
    if(thiz != NULL)
	{
        const char* auth_conf[4] = 
        {
            "noauth",
            "-chap -mschap -mschap-v2 refuse-eap",
            "-pap -mschap -mschap-v2 refuse-eap",
            "-mschap -mschap-v2 refuse-eap"
        };

		property_set("net.gprs.user", user);
		property_set("net.gprs.password", pwd);
		property_set("net.gprs.device", device);
		property_set("net.gprs.ppp-exit", "");
        property_set("net.gprs.number", number);
            
        if(authtype < 4)
        {
            property_set("net.gprs.authtype", auth_conf[authtype]);
        }
        else
        {
            property_set("net.gprs.authtype", auth_conf[0]);
        }
		
		//TODO
		property_set("ctl.stop", "pppd_gprs");
		property_set("ctl.stop", "dongle_pppd_gprs");
		sleep(1);
		property_set("ctl.start", thiz->with_thread ? "dongle_pppd_gprs" : "pppd_gprs");
		sleep(1);
		
#ifdef ENABLE_LOG
		ALOGD("%s: thiz: %p started_poll=%d, with_thread:%d", __func__, thiz, thiz->started_poll, thiz->with_thread);
#endif

		if(!thiz->started_poll)
		{
			if(thiz->with_thread == 0)
			{
				RIL_requestTimedCallback(ppp_poll_state, thiz, &TIMEVAL_GPRSTPOLL);
			}
			else
			{
				pthread_attr_t attr;
				pthread_attr_init (&attr);
				pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
				pthread_create(&thiz->tid_pppd_monitor, &attr, pppdStateMonitor, thiz);
			}

			thiz->started_poll = 1;
		}
	}


    return;
}

void		ppp_ctrl_start_pppd(PppCtrl* thiz, const char* device, 
				const char* user, const char* pwd, int authtype)
{
    ppp_ctrl_start_pppd_with_call(thiz, device, user, pwd, NULL, authtype);
}

PppCtrl*    ppp_ctrl_create_with_thread(OnPppStateChangedFunc onStateChanged, void* user_data)
{
	PppCtrl* thiz = ppp_ctrl_create(onStateChanged, user_data);

	thiz->with_thread = 1;

	return thiz;
}

void		ppp_ctrl_stop_pppd(PppCtrl* thiz)
{
	int ret = property_set("ctl.stop", thiz->with_thread ? "dongle_pppd_gprs" : "pppd_gprs");
	sleep(2);
	ALOGD("%s ret: %d line:%d", __func__, ret, __LINE__);
}
